#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <dirent.h>
#include <unistd.h>
#include <vector>
#include <sstream>
#include <fstream>
#include <sys/io.h>
#include <sys/times.h>
#include <iomanip> // setw()
using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;

#include "calTwoPoints.h"

namespace segfg 
{
/////////////////////////////////////////////////
struct sARct {
	std::vector<cv::Point> bounder;
	std::vector<cv::Point> poly;
	int shortIdx[2];
	int longIdx[2];
	double angle;
	std::vector<std::vector<cv::Point> > oc; // Optical Character
	
	sARct () {
		bounder.clear();
		poly.clear();
		oc.clear();
	}
};

struct sPara {
	int minW, maxW, minH, maxH, minArea, maxArea;
	sPara () {
		minW = 20;
		maxW = 200;
		minH = 20;
		maxH = 200;
		minArea = 20;
		maxArea = 40000;
	}
};

struct sAFlag {
	int bounderIdx;
	std::vector<int> ocIdx;
	sAFlag () {
		bounderIdx = -1;
		ocIdx.clear();
	}
};

struct sAPosi {
	std::vector<cv::Point> bounder;
	std::vector<cv::Point> poly;
	std::vector<std::vector<cv::Point> > ocBounders;
	std::vector<cv::Mat> ocImgs; // croped and resize images for cnn
	std::vector<int > ocNums; // result of cnn;
};

struct DistanceIdx {
	double distance;
	int idx;
};

bool sort_dist(const DistanceIdx v1, const DistanceIdx v2)
{
	return v1.distance > v2.distance; 
}

/////////////////////////////////////////////////
bool CornerPointDetection(cv::Mat src, 
                          cv::Mat mask, 
                          vector<Point2f> &corners);
bool ApproximatePolygonal(const std::vector<Point> &contours, 
						  std::vector<Point> &approxCurve);
bool construct(const vector<vector<Point> > &contours,
	           const vector<Vec4i> &hierarchy,
	           const int i,
	           const int k,
	           const vector<Point> &poly,
	           sARct &curR);
bool blobAnalysis(cv::Mat &src, vector<vector<Point> > &contours, 
				  vector<Vec4i> &hierarchy, int i, sPara &para, 
				  std::vector<Point> &BounderPoly, int &ocFirstIdx);
bool sortPoly(std::vector<Point> &poly);
std::vector<int> get_oc(const vector<vector<Point> > &contours,
                        const vector<Vec4i> &hierarchy, 
                        int k,
                        const std::vector<Point> &BounderPoly);
std::vector<cv::Mat> affine_transform(Mat src, 
                                      const sAFlag &curFg, 
                                      const vector<vector<Point> > &contours, 
                                      const vector<Vec4i> &hierarchy,
                                      std::vector<Point> &BounderPoly);
bool process(cv::Mat &src, std::vector<sARct> &regs);
sAPosi segmentFlag(cv::Mat &src);

/*******************************
*
*/
bool segmentFlag(cv::Mat &src, vector<sAPosi> &regs) 
{
	// must be a single channel image
	CV_Assert(CV_MAT_TYPE(src.type()) == CV_8UC1);
	sPara para;
	// otsu 
	cv::Mat tmp;
	cv::threshold(src, tmp, 255, 255, THRESH_BINARY_INV | THRESH_OTSU);
	// erase regions with huge size
	int an = 3;
	cv::Mat element = getStructuringElement(MORPH_RECT, 
					  cv::Size(an*2+1, an*2+1), cv::Point(an,an));
	cv::Mat mor;
	cv::morphologyEx(tmp, mor, MORPH_OPEN, element);
	cv::subtract(tmp, mor, tmp);
	mor.release();
	// find contours
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	findContours(tmp, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
	vector<sAFlag> Fgs;
	regs.clear();
	for (int i = 0; i < (int)contours.size(); i++) {
		int k = -1;
		std::vector<Point> BounderPoly;
		if (blobAnalysis(src, contours, hierarchy, i, para, BounderPoly, k) == true) {
			// sort polygon points
			sortPoly(BounderPoly);
			// construct a region
			sAPosi curPosi;
			curPosi.bounder = contours[i];
			curPosi.poly = BounderPoly;
			sAFlag curFg;
			curFg.bounderIdx = i;
			curFg.ocIdx = get_oc(contours, hierarchy, k, BounderPoly);
			Fgs.push_back(curFg);
			// affine transform
			std::vector<Mat> curMat = affine_transform(src, curFg, contours, hierarchy, BounderPoly);
			curPosi.ocImgs.insert(curPosi.ocImgs.end(), curMat.begin(), curMat.end());
			curPosi.ocNums.push_back(-1);
			for (int j = 0; j < (int)curFg.ocIdx.size(); j++) {
				curPosi.ocBounders.push_back(contours[curFg.ocIdx[j]]);
			}
			regs.push_back(curPosi);
		}
	}
/*
	for (int j = 0; j < (int)ocImgs.size(); j++) {
		stringstream ss; ss << "./z"; ss << j; ss << ".jpg";
		imwrite(ss.str(), ocImgs[j]);
	}
*/
	return true;
}

std::vector<cv::Mat> affine_transform(Mat src, 
                                      const sAFlag &curFg, 
                                      const vector<vector<Point> > &contours, 
                                      const vector<Vec4i> &hierarchy,
                                      std::vector<Point> &BounderPoly)
{
	std::vector<cv::Mat> ocImg;

	// prepare affine matrix
	Rect curRct = boundingRect(BounderPoly);
	int nD = 50;
	Rect dilRct = curRct;
	dilRct.x -= nD;
	dilRct.y -= nD;
	dilRct.width += nD*2;
	dilRct.height += nD*2;
//	Mat roi = src(dilRct);

	int si = (int)BounderPoly.size();
	std::vector<Point> crpVct(si);
	for (int i = 0; i < si; i++) {
		crpVct[i].x = BounderPoly[i].x - dilRct.x;
		crpVct[i].y = BounderPoly[i].y - dilRct.y;
	}
	
	Point2f srcTri[3];
    Point2f dstTri[3];
	srcTri[0] = crpVct[0];
    srcTri[1] = crpVct[1];
    srcTri[2] = crpVct[2];
    dstTri[0] = Point2f(nD, nD);
    dstTri[1] = Point2f(nD, dilRct.height - nD);
    dstTri[2] = Point2f(dilRct.width - nD, dilRct.width - nD);
	Mat warp_mat = getAffineTransform( srcTri, dstTri);
//	Mat tmp;
//	warpAffine(roi, tmp, warp_mat, Size(dilRct.width, dilRct.height));
//	imshow("win", tmp);
//	waitKey(0);
//	return ocImg;
	
	for (int i = 0; i < (int)curFg.ocIdx.size(); i++) {
		int k = curFg.ocIdx[i];
		// prepare and affine image
		Mat tmp(Size(dilRct.width, dilRct.height), CV_8UC1, Scalar::all(0));
		drawContours(tmp, contours, k, Scalar(255), -1, LINE_8, hierarchy, INT_MAX, Point(-dilRct.x, -dilRct.y));
		warpAffine(tmp, tmp, warp_mat, Size(dilRct.width, dilRct.height));
		// affine points
		std::vector <Point2f> crpBounder((int)contours[k].size());
		for (int j =0; j < (int)contours[k].size(); j++) {
			crpBounder[j].x = contours[k][j].x - dilRct.x;
			crpBounder[j].y = contours[k][j].y - dilRct.y;
		}
		cv::transform(crpBounder, crpBounder, warp_mat);

		Rect crpRct = boundingRect(crpBounder);
		Rect crpDil = crpRct;
		crpDil.x -= crpRct.width / 2;
		crpDil.y -= crpRct.height / 2;
		crpDil.width += crpRct.width;
		crpDil.height += crpRct.height;
//		rectangle(tmp, Point(crpDil.x, crpDil.y), Point(crpDil.x + crpDil.width,
//		          crpDil.y + crpDil.height), Scalar(255), 1);
		Mat curOC;
		resize(tmp(crpDil), curOC, Size(28, 28));
		ocImg.push_back(curOC);
	}
	
	return ocImg;
}

std::vector<int> get_oc(const vector<vector<Point> > &contours,
                        const vector<Vec4i> &hierarchy, 
                        int k, 
                        const std::vector<Point> &BounderPoly)
{
	RotatedRect box = minAreaRect(BounderPoly);
	std::vector<int> ocIdx;
	ocIdx.clear();
	int m = k;
	// next
	while (true) {
		RotatedRect curBox = minAreaRect(contours[m]);
		if (max(curBox.size.width, curBox.size.height) > min(box.size.width / 2, box.size. height / 2)) 
			ocIdx.push_back(m);
		if (hierarchy[m][1] >= 0) {
			m = hierarchy[m][1];
		} 
		else break;
	}
	// previous
	m = k;
	while (true) {
		if (hierarchy[m][0] >= 0) {
			m = hierarchy[m][0];
			RotatedRect curBox = minAreaRect(contours[m]);
			if (max(curBox.size.width, curBox.size.height) > min(box.size.width / 2, box.size. height / 2)) 
				ocIdx.push_back(m);
		} 
		else break;
	}
	
	// sort 
	vector<DistanceIdx> di;
	for (int i = 0; i < (int)ocIdx.size(); i++) {
		DistanceIdx cur;
		RotatedRect box = minAreaRect(contours[ocIdx[i]]);
		cur.distance = fabs(BounderPoly[3].x - box.center.x) + fabs(BounderPoly[3].y - box.center.y);
		cur.idx = ocIdx[i];
		di.push_back(cur);
	}
	cout << "+++++++++++++++" << endl;
	for (int i = 0; i < (int)ocIdx.size(); i++) {
		cout << setw(8) << di[i].distance << setw(8) << di[i].idx << endl;
	}
	
	std:sort(di.begin(), di.end(), sort_dist);
	for (int i = 0; i < (int)ocIdx.size(); i++) {
		ocIdx[i] = di[i].idx;
	}
	cout << "--" << endl;
	for (int i = 0; i < (int)ocIdx.size(); i++) {
		cout << setw(8) << di[i].distance << setw(8) << di[i].idx << endl;
	}
	return ocIdx;
}

bool blobAnalysis(cv::Mat &src, vector<vector<Point> > &contours, 
				  vector<Vec4i> &hierarchy, int i, sPara &para, 
				  std::vector<Point> &BounderPoly, int &ocFirstIdx) 
{
	cv::Rect curRect = boundingRect(contours[i]);
	double curArea = contourArea(contours[i]);
	// select according the size or area
	if (curRect.width < para.minW || curRect.height < para.minH || 
		curRect.width > para.maxW || curRect.height > para.maxH || 
		curArea < para.minArea || 
		curArea > para.maxArea) 
	{
		return false;
	}
	// whether there are child region inside current region
	int HaveChildRegion = false;
	int j = -1, k = -1;
	if (hierarchy[i][2] >= 0 && hierarchy[i][3] < 0) {
		j = hierarchy[i][2];
		if (hierarchy[j][2] >= 0) {
			k = hierarchy[j][2];
			if (false) { // debug code
				Mat abc; 
				cvtColor(src, abc, COLOR_GRAY2BGR);
				drawContours(abc, contours, i, Scalar(255,0,0), 
							 1, LINE_8, hierarchy);
				drawContours(abc, contours, j, Scalar(0,255,0), 
							 1, LINE_8, hierarchy);
				drawContours(abc, contours, k, Scalar(0,0,255), 
							 1, LINE_8, hierarchy);
				imwrite("./z.jpg", abc);
				abc.release();
			}
			HaveChildRegion = true;
		}
	}
	if (HaveChildRegion == false) {
		return false;
	} 
	// select from the simplicity of geometry
	vector<vector<Point> > poly(1);
	approxPolyDP(contours[i], poly[0], 3, true);
	int n = (int)poly[0].size();
	if (n != 5) {
		return false;
	}
	// geometry shape analysising
    Point2f vtx[4];
    RotatedRect box = minAreaRect(contours[i]);
    box.points(vtx);
    double rectangularity = curArea / (box.size.width * box.size.height);
	if(rectangularity < 0.65) {
		return false;
	} 
	
	BounderPoly = poly[0];
	ocFirstIdx = k;
	return true;
}

bool sortPoly(std::vector<Point> &poly)
{
	int sizeP = (int)poly.size();
	std::vector<Point> sortvct(sizeP);
	Point pt0 = poly[sizeP - 1];
	int minidx; double minDis = 999999.f;
	int maxidx; double maxDis = 0.f;
	for (int j = 0; j < sizeP; j++) {
		Point pt1 = poly[j];
		double distance;
		distance = powf((pt0.x - pt1.x),2) + powf((pt0.y - pt1.y),2);
		distance = sqrtf(distance); 
		if (distance < minDis) {
			minDis = distance;
			minidx = j;
		}
		if (distance > maxDis) {
			maxDis = distance;
			maxidx = j;
		}
		pt0 = pt1;
	}
	// sort
	if (minidx - 1 < 0) 
		sortvct[3] = poly[sizeP - 1];
	else 
		sortvct[3] = poly[minidx - 1];
	sortvct[4] = poly[minidx];
	
	if (maxidx - 1 < 0)
		sortvct[1] = poly[sizeP - 1];
	else
		sortvct[1] = poly[maxidx - 1];
	sortvct[2] = poly[maxidx];
	
	if (minidx + 1 > sizeP - 1) 
		sortvct[0] = poly[0];
	else
		sortvct[0] = poly[minidx + 1];
	
	poly.clear();
	poly = sortvct;
	return true;
}

/***************************************************
* src 	- input image
*/
bool process(cv::Mat &src, std::vector<sARct> &regs) 
{
	regs.clear();
	CV_Assert(CV_MAT_TYPE(src.type()) == CV_8UC1);
	sPara para;
	// binary
	cv::Mat tmp;
	cv::threshold(src, tmp, 255, 255, THRESH_BINARY_INV | THRESH_OTSU);
	
	// openning and subtract. this can erase contours with huge size
	int an = 3;
	cv::Mat element = getStructuringElement(MORPH_RECT, 
					  cv::Size(an*2+1, an*2+1), cv::Point(an,an));
	cv::Mat mor;
	cv::morphologyEx(tmp, mor, MORPH_OPEN, element);
	cv::subtract(tmp, mor, tmp);
	mor.release();
	// blob analysising
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	findContours(tmp, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
	for (int i = 0; i < (int)contours.size(); i++) {
		int k = -1;
		std::vector<Point> BounderPoly;
		if (blobAnalysis(src, contours, hierarchy, i, para, BounderPoly, k) == true) {
			// construct a region
			sARct curR;
			construct(contours, hierarchy, i, k, BounderPoly, curR);
			regs.push_back(curR);
		}
	}
	
	return true;
}

bool construct(const vector<vector<Point> > &contours,
	           const vector<Vec4i> &hierarchy,
	           const int i,
	           const int k,
	           const vector<Point> &poly,
	           sARct &curR)
{
	// original contour points
	curR.bounder = contours[i];
	
	// polygon points
	std::vector<Point> sortvct;
	sortvct = poly;
	sortPoly(sortvct);
	curR.poly.clear();
	curR.poly = sortvct;
	
	// compute the angle
	switch (111) { 
	case 0:{
		std::vector<Point2f> sizes;
		int a = curR.longIdx[0];
		int b = curR.longIdx[1];
		Point pt0 = curR.poly[a];
		Point pt1 = curR.poly[b];
		sizes.push_back(Point2f(pt0.x, pt0.y));
		sizes.push_back(Point2f(pt1.x, pt1.y));
		Mat xpts(sizes.size(), 1, CV_32F, &sizes[0].x, 2*sizeof(float));
		Mat ypts(sizes.size(), 1, CV_32F, &sizes[0].y, 2*sizeof(float));
		Mat magnitude, angle;
		cartToPolar(xpts, ypts, magnitude, angle);
		cout << ">> angle0 = " << angle.t()*180.f/CV_PI << endl;
		break;
	}
	case 1:
		int a = curR.longIdx[0];
		int b = curR.longIdx[1];
		Point pt0 = curR.poly[a];
		Point pt1 = curR.poly[b];
		calTwoPoint tp(pt0, pt1);
		curR.angle = tp.get_angle();
		cout << ">> angle1 = " << curR.angle << endl;
	}

	int m = k;
	while (true) {
		curR.oc.push_back(contours[m]);
		if (hierarchy[m][1] >= 0) {
			m = hierarchy[m][1];
		} 
		else break;
	}
	m = k;
	while (true) {
		if (hierarchy[m][0] >= 0) {
			m = hierarchy[m][0];
			curR.oc.push_back(contours[m]);
		} 
		else break;
	}
	
	return true;
}

bool ApproximatePolygonal(const std::vector<Point> &contours, 
                          std::vector<Point> &approxCurve)
{
	vector<vector<Point> > poly(1);
	// Parameter specifying the approximation accuracy. 
	// This is the maximum distance between the 
	// original curve and its approximation.
	double epsilon = 3;
	// If true, the approximated curve is closed (its first and 
	// last vertices are connected). Otherwise, it is not closed. 
	bool closed = true;
	approxPolyDP(contours, poly[0], epsilon, closed);
	approxCurve = poly[0];
	return true;
}

/************************************
* src 		Input 8-bit or floating-point 32-bit, single-channel image
* make		Optional region of interest
* corners	Output vector of detected corners
*/
bool CornerPointDetection(cv::Mat src, cv::Mat mask, vector<Point2f> &corners)
{
	if (src.empty()) {
		return false;
	}
	
	double maxCorners = 20;
	double qualityLevel = 0.01;
	double minDistance = 10;
	int blockSize = 3;
    bool useHarrisDetector = false;
    double k = 0.04;
    
    cv::Mat tmp = src.clone();
	goodFeaturesToTrack(src,
						corners,
						maxCorners,
						qualityLevel,
						minDistance,
						mask,
						blockSize,
						useHarrisDetector,
						k);
	return true;
}

} // namespace segfg

